package com.medical.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.medical.common.to.OrderTo;
import com.medical.common.utils.R;
import com.medical.seckill.feign.GoodsClient;
import com.medical.seckill.service.SeckillService;
import com.medical.seckill.to.RedisGoodsTo;
import com.medical.seckill.vo.GoodsVo;
import com.medical.seckill.vo.SeckillGoodsvo;
import com.medical.seckill.vo.SessionAndGoodsVo;
import com.medical.seckill.to.SessionTo;
import com.sun.corba.se.spi.orbutil.threadpool.Work;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //活动redis前缀
    private final String SESSION_REDIS_PREFIX="seckill:session:";
    //商品redis前缀
    private final String GOODS_REDIS_PREFIX="seckill:goods";
    //库存前缀
    private final String REDISSION_SEMAPHORE="seckill:stock:";

    /**
     * 上架商品
     */


    @Override
    public void findUploadSeckill() {
        /**
         * feign调用获取两天内上架的商品
         */
        R r = goodsClient.getSession2daysGoodsId();
        if(r.getCode()==0){
            List<SessionAndGoodsVo> sessiondata = r.getData("data",new TypeReference<List<SessionAndGoodsVo>>() {
            });
            //1、存储活动信息和每次多动对应的商品id
            setRedisSessionAndGoodsId(sessiondata);
            //2、存储活动对应的商品
            setRedisGoods(sessiondata);
        }


    }


    /**
     * 存储活动信息
     * @param sessionData
     */

    public void setRedisSessionAndGoodsId(List<SessionAndGoodsVo> sessionData){
        for (SessionAndGoodsVo session : sessionData) {
            Long startTime = session.getStartTime().getTime();
            Long endTime = session.getEndTime().getTime();
            String  key=SESSION_REDIS_PREFIX+startTime+"_"+endTime;
            //redis中没有改键才存
            if(!redisTemplate.hasKey(key)&&session.getRelationEntities()!=null){
                List<SeckillGoodsvo> goodsvos = session.getRelationEntities();
                //获取秒杀商品的id存入redis中
                List<String> goodIds = goodsvos.stream().map(item -> {
                    String goodsId = item.getSessionId()+"_"+item.getGoodsId().toString();
                    return goodsId;
                }).collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key,goodIds);
            }
        }
    }

    /**
     * 存储商品信息
     * @param sessionData
     */
    public void setRedisGoods(List<SessionAndGoodsVo> sessionData){
        for (SessionAndGoodsVo sessionDatum : sessionData) {
            //绑定hash，传入将商品信息传入redis中
            BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(GOODS_REDIS_PREFIX);
            //获取活动对应的所有商品
            sessionDatum.getRelationEntities().stream().forEach(goods -> {
                String redisKey=sessionDatum.getId().toString()+"_"+goods.getGoodsId().toString();
                if(!hashOps.hasKey(redisKey)){
                    Integer goodsId = goods.getGoodsId();
                    RedisGoodsTo redisGoodsTo = new RedisGoodsTo();
                    //1、feign调用获取对应的的商品详情
                    R echo = goodsClient.echo(goodsId);
                    if(echo.getCode()==0){
                        GoodsVo goodsVo = echo.getData("commodity",new TypeReference<GoodsVo>() {
                        });
                        redisGoodsTo.setGoodsVo(goodsVo);
                    }
                    //2、存取商品的秒杀信息
                    BeanUtils.copyProperties(goods,redisGoodsTo);
                    //3、设置随机值，每当商品开始秒杀的时候我们就传给一个随机玛给商品
                    String token = UUID.randomUUID().toString().replace("_", "");
                    redisGoodsTo.setRandomCode(token);
                    //4、设置库存，将redssion的信号信号量作为商品的库存进行存储
                    RSemaphore semaphore = redissonClient.getSemaphore(REDISSION_SEMAPHORE+token);
                    semaphore.trySetPermits(goods.getSeckillCount());
                    //5、上传秒杀时间
                    redisGoodsTo.setStartTime(sessionDatum.getStartTime().getTime());
                    redisGoodsTo.setEndTime(sessionDatum.getEndTime().getTime());
                    //6、存入redis中
                    String goodsInfo = JSON.toJSONString(redisGoodsTo);
                    hashOps.put(redisKey,goodsInfo);
                }
            });
        }
    }

    /**
     * 获取当前正在秒杀的商品
     * @return
     */
    @Override
    public List<RedisGoodsTo> getCurrentGoodsAndSession() {
        //获取当前秒杀场次的id
        //1、获取所有key值为SESSION_REDIS_PREFIX的值
        long currentTimeMillis = System.currentTimeMillis();
        Set<String> keys = redisTemplate.keys(SESSION_REDIS_PREFIX+"*");
        for (String key : keys) {
            //seckill:session:1630238400000_1630332000000
            String replace = key.replace(SESSION_REDIS_PREFIX, "");
            String[] split = replace.split("_");
            //获取商品的开始时间和结束时间
            Long startTime=Long.parseLong(split[0]);
            Long endTime=Long.parseLong(split[1]);
            //判断当前时间是否在这个秒杀时间的区间
            if(currentTimeMillis>=startTime&&currentTimeMillis<=endTime){
                //获取到当前活动对应的商品id
                List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(GOODS_REDIS_PREFIX);
                assert range!=null;//断言类，类似于判断，系统运行是range只能是true，如果是false则抛出异常
                //批量获取该商品id范围下所有的value值也就是商品的详情
                List<String> strings = hashOps.multiGet(range);
                if(strings!=null&&strings.size()>0){
                    List<RedisGoodsTo> collect = strings.stream().map(item -> {
                        RedisGoodsTo redisGoodsTo = JSON.parseObject((String)item, RedisGoodsTo.class);
                        return redisGoodsTo;
                    }).collect(Collectors.toList());
                    return collect;
                }
                break;
            }
        }
        return null;
    }

    /**
     * 获取活动所对应的秒杀商品
     * @param key
     * @return
     */
    @Override
    public List<RedisGoodsTo> getGoodsBySession(String key) {
        Set<String> keys = redisTemplate.keys(SESSION_REDIS_PREFIX + "*");
        for (String s : keys) {
            //找到对应的场次
            if (s.equals(key)){
                List<String> goodsId = redisTemplate.opsForList().range(s, -100, 100);
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(GOODS_REDIS_PREFIX);
                List<String> strings = hashOps.multiGet(goodsId);
                List<RedisGoodsTo> collect = strings.stream().map(item -> {
                    RedisGoodsTo redisGoodsTo = JSONObject.parseObject(item, RedisGoodsTo.class);
                    redisGoodsTo.getGoodsVo().setSeckillPrice(redisGoodsTo.getSeckillPrice());
                    return redisGoodsTo;
                }).collect(Collectors.toList());
                return collect;
            }
        }
        return null;
    }

    @Override
    public List<SessionTo> getSession() {
        long currentTimeMillis = System.currentTimeMillis();
        Set<String> keys = redisTemplate.keys(SESSION_REDIS_PREFIX + "*");
        List<String> sessionsId = new ArrayList<>();
        //获取所有的场次信息，因为同一场次有多个商品会参加秒杀，存入redis时会出现重复的场次id，所有在这里，我们对场次id进行了去重批量查询
        for (String key : keys) {
            List<String> range = redisTemplate.opsForList().range(key, -100, 100);
            for (String s : range) {
                String[] s1 = s.split("_");
                sessionsId.add(s1[0]);
            }
        }
        R r = goodsClient.listAll(sessionsId);
        //获取到的场次 data
        List<SessionTo> data = r.getData("data", new TypeReference<List<SessionTo>>() {
        });
        for (SessionTo datum : data) {
            //检测到此时的秒杀场次为正在秒杀则，修改场次状态
            long startTime = datum.getStartTime().getTime();
            long endTime = datum.getEndTime().getTime();
            if(currentTimeMillis>=startTime&&currentTimeMillis<=endTime){
                datum.setStatus("1");
            }else if(currentTimeMillis>=endTime){
                datum.setStatus("2");
            }
            for (String key : keys) {
                String[] split = key.split(":");
                String[] s = split[2].split("_");//截取到开始时间s[0]结束时间s[1]
                long start = Long.parseLong(s[0]);
                long end = Long.parseLong(s[1]);
                if(datum.getStartTime().getTime()==start&&datum.getEndTime().getTime()==end){
                    datum.setKey(key);
                }
            }
        }
        return data;
    }

    /**
     * 开始秒杀
     * @param kid 1_1 1_2
     * @param num
     * @param randomCode
     * @return
     */

    @Override
    public String startKill(String kid, Integer num, String randomCode) {
        long currentTimeMillis = System.currentTimeMillis();
        //1、todo 获取当前用户登录信息
        //2、获取当前商品详情
        BoundHashOperations<String, String, String> hashops = redisTemplate.boundHashOps(GOODS_REDIS_PREFIX);
        String s = hashops.get(kid);
        if(StringUtils.isEmpty(s)){
            return null;//表示没有改秒杀商品
        }
        //3、获得商品实体类开始校验各项数据
        RedisGoodsTo goodsTo = JSONObject.parseObject(s, RedisGoodsTo.class);
        Long startTime = goodsTo.getStartTime();//商品开始秒杀时间
        Long endTime = goodsTo.getEndTime();//商品结束秒杀时间
        //3.1、时间校验 
        if(currentTimeMillis>=startTime&&currentTimeMillis<=endTime){
            //3.2、随机码校验
            if(goodsTo.getRandomCode().equals(randomCode)){
                //3.3、商品id校验
                String goodsId=goodsTo.getSessionId()+"_"+goodsTo.getGoodsVo().getId();
                if(kid.equals(goodsId)){
                    //3.4、超买超卖校验
                    String s1 = redisTemplate.opsForValue().get(REDISSION_SEMAPHORE+randomCode);
                    int stock = Integer.parseInt(s1);
                    //购买数量不能超过信号量（库粗）&&不能超过购买限度
                    if(stock>0&&num<stock&&stock<=goodsTo.getSeckillCount()){
                        //todo 幂等性处理 判断这人有没有重复点击购买
                        Boolean aBoolean=true;
                        if(aBoolean){
                            //占位成功，开始进行秒杀，减库存，给消息队列发请求，生成订单
                            RSemaphore semaphore = redissonClient.getSemaphore(REDISSION_SEMAPHORE+randomCode);
                            try {
                                //库存预减
                                boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                if (b){
                                    //生成订单发给队列
                                    String orderId = IdWorker.getIdStr();
                                    OrderTo orderTo = new OrderTo();
                                    orderTo.setCommodityId(goodsTo.getGoodsVo().getId());
                                    orderTo.setCount(num);
                                    orderTo.setOrderCoding(orderId);
                                    orderTo.setOrderState("等待付款");
                                    orderTo.setCreatedate(new Date());
                                    orderTo.setDeliverState("0");
                                    orderTo.setPayState("0");
                                    orderTo.setTakeState("0");
                                    BigDecimal decimal = goodsTo.getSeckillPrice().multiply(new BigDecimal(num));
                                    orderTo.setSumPrice(decimal);//生成订单完成发送给队列
                                    rabbitTemplate.convertAndSend("seckill-event-exchange","seckill.release.order",orderTo);
                                    return orderId;
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    return null;
                }
            }

        }

        return null;
    }
    

}
